/******************************************************************************
 * FileName:    gwin_board.c
 * Desc:        板载
 *
 *
 * Author:      Lin
 * Notes:
 *
 ******************************************************************************/

/*-------------------------- Includes ---------------------------------------*/
#include "gwin_board.h"
#include "miio_user_api.h"


/*-------------------------- Macro Definitions ------------------------------*/

/*-------------------------- Type Declarations ------------------------------*/

/*-------------------------- Global Variables -------------------------------*/

/*-------------------------- Local Variables --------------------------------*/

/*-------------------------- Functions Declarations -------------------------*/
static void gwin_watch_dog_init(void);
static void gwin_led_init(void);
static void gwin_btn_init(void);
static void gwin_encoder_init(void);
static void gwin_motor_init(void);
static void gwin_uart_init(void);


/*-------------------------- Functions Definitions --------------------------*/
/**
 * @brief 板载初始化
 */
void gwin_board_init(void)
{
    gwin_watch_dog_init();
    gwin_led_init();
    gwin_btn_init();
    gwin_encoder_init();
    gwin_motor_init();
    gwin_uart_init();
}


/*----------- WDG -----------*/
static int watch_dog_fd = -1;

/**
 * @brief 看门狗 初始化
 */
static void gwin_watch_dog_init(void)
{
    watch_dog_fd = open("/dev/watchdog0", O_RDWR);
    if (watch_dog_fd < 0)
    {
        printk("watch dog open error!!!\r\n");
    }
    ioctl(watch_dog_fd, WDIOC_SETTIMEOUT, GWIN_WDG_TIMEOUT);
    ioctl(watch_dog_fd, WDIOC_START, 0);
}

/**
 * @brief 看门狗 喂狗
 */
void gwin_watch_dog_feed(void)
{
    ioctl(watch_dog_fd, WDIOC_KEEPALIVE, 0);
}

/**
 * @brief 看门狗 重启
 */
uint8_t gwin_watch_dog_reboot(void)
{
    return mible_mesh_get_reset_reason();
}


/*----------- 指示灯 -----------*/
static int output_led_fd = -1;

/**
 * @brief 初始化 指示灯
 */
static void gwin_led_init(void)
{
    output_led_fd = open("/dev/gpio"_TO_STR(GWIN_GPIOx_LED), O_RDWR);
    if (output_led_fd < 0)
    {
        printk("led open error!!!\r\n");
    }
}

/**
 * @brief 设置 指示灯
 */
void gwin_led_set_state(uint8_t on)
{
    ioctl(output_led_fd, GPIOC_WRITE, on > 0);
}


/*----------- 按键 -----------*/
static int input_btn_fd[GWIN_DEV_BTN_CNT_MAX] = { [0 ... GWIN_DEV_BTN_CNT_MAX - 1] = -1, };
static const uint8_t input_btn_gpiox[GWIN_DEV_BTN_CNT_MAX] = { GWIN_GPIOx_KEY, GWIN_GPIOx_SW_1, GWIN_GPIOx_SW_2 };

/**
 * @brief 初始化 按键
 */
static void gwin_btn_init(void)
{
    char path[16];
    for (uint8_t i = 0; i < GWIN_DEV_BTN_CNT_MAX; i++)
    {
        memset(path, 0x00, sizeof(path));
        snprintf(path, sizeof(path), "%s%d", "/dev/gpio", input_btn_gpiox[i]);
        input_btn_fd[i] = open(path, O_RDWR);
        if (input_btn_fd[i] < 0)
        {
            printk("btn gpio open error!!! %d\r\n", input_btn_gpiox[i]);
        }
    }

}

/**
 * @brief 按键获取
 */
uint8_t gwin_btn_get_state(uint8_t btn_index)
{
    uint8_t ret = 1;

    if (btn_index < GWIN_DEV_BTN_CNT_MAX && input_btn_fd[btn_index] >= 0)
    {
        ioctl(input_btn_fd[btn_index], GPIOC_READ, (unsigned long)((uintptr_t)&ret));
    }

    return (ret == 0);
}


/*----------- 编码器 -----------*/
static int input_encoder_fd[GWIN_DEV_ENCODER_CNT_MAX] = { [0 ... GWIN_DEV_ENCODER_CNT_MAX - 1] = -1, };
static const uint8_t input_encoder_gpiox[GWIN_DEV_ENCODER_CNT_MAX] = { GWIN_GPIOx_ENCODER_1, GWIN_GPIOx_ENCODER_2 };

/**
 * @brief 初始化 编码器
 */
static void gwin_encoder_init(void)
{
    char path[16];
    for (uint8_t i = 0; i < GWIN_DEV_ENCODER_CNT_MAX; i++)
    {
        memset(path, 0x00, sizeof(path));
        snprintf(path, sizeof(path), "%s%d", "/dev/gpio", input_encoder_gpiox[i]);
        input_encoder_fd[i] = open(path, O_RDWR);
        if (input_encoder_fd[i] < 0)
        {
            printk("encoder gpio open error!!! %d\r\n", input_encoder_gpiox[i]);
        }
    }
}

/**
 * @brief 编码器获取
 */
uint8_t gwin_encoder_get_state(uint8_t encoder_index)
{
    uint8_t ret = 1;

    if (encoder_index < GWIN_DEV_ENCODER_CNT_MAX && input_encoder_fd[encoder_index] >= 0)
    {
        ioctl(input_encoder_fd[encoder_index], GPIOC_READ, (unsigned long)((uintptr_t)&ret));
    }

    return (ret == 0);
}


/*----------- 电机 -----------*/
static struct pwm_info_s pwm_info_motor[GWIN_DEV_MOTOR_CNT_MAX] = { [0 ... GWIN_DEV_MOTOR_CNT_MAX - 1] = { .frequency = 1000, .duty = 0, }, };
static int pwm_motor_fd[GWIN_DEV_MOTOR_CNT_MAX] = { [0 ... GWIN_DEV_MOTOR_CNT_MAX - 1] = -1, };
static const uint8_t pwm_motor_chx[GWIN_DEV_MOTOR_CNT_MAX] = { GWIN_PWMx_MOTOR_1, GWIN_PWMx_MOTOR_2, };


/**
 * @brief 电机 初始化
 */
static void gwin_motor_init(void)
{
    char path[16];
    for (uint8_t i = 0; i < GWIN_DEV_MOTOR_CNT_MAX; i++)
    {
        memset(path, 0x00, sizeof(path));
        snprintf(path, sizeof(path), "%s%d", "/dev/pwm", pwm_motor_chx[i]);
        pwm_motor_fd[i] = open(path, O_RDWR);
        if (pwm_motor_fd[i] < 0)
        {
            printk("motor pwm open error!!! %d\r\n", pwm_motor_chx[i]);
        }
        ioctl(pwm_motor_fd[i], PWMIOC_SETCHARACTERISTICS, &pwm_info_motor[i]);
        ioctl(pwm_motor_fd[i], PWMIOC_START, 0);
    }
}

/**
 * @brief 设置PWM
 */
static void gwin_miot_set_pwm(int *fd, uint8_t pwm_chanel, struct pwm_info_s *pwm_info, uint32_t pwm_duty)
{
    pwm_duty = MIN(pwm_duty, PWM_CNT_MAX);          // 限制最大值

    if (*fd > 0)
    {
        pwm_info->duty = pwm_duty;

        // 防止最后1级不亮灯（亮度1%色温4100K-5000K设备显示为灭灯的状态）
        uint32_t pwm_duty_min = (PWM_CNT_MAX / PWM_FREQUENCY_CNT_MAX(pwm_info->frequency) + 1) * 2;
        if (pwm_duty > 0 && pwm_info->duty < pwm_duty_min)
        {
            pwm_info->duty = pwm_duty_min;
        }

        ioctl(*fd, PWMIOC_SETCHARACTERISTICS, pwm_info);
    }
}

/**
 * @brief 电机 设置PWM
 */
void gwin_motor_set_pwm(uint8_t motor_index, uint32_t pwm)
{
    if (motor_index < GWIN_DEV_MOTOR_CNT_MAX && pwm_motor_fd[motor_index] > 0)
    {
        gwin_miot_set_pwm(&pwm_motor_fd[motor_index], pwm_motor_chx[motor_index], &pwm_info_motor[motor_index], pwm);
    }
}


/*----------- 串口 -----------*/
static int uart_fd = -1;
static uint16_t uart_rx_len = 0;
static uint8_t uart_rx_buf[GWIN_UART_MAX_LEN] = { 0x00 };
static uint64_t rx_clock_last = 0;
static int8_t (*_uart_recv_data_parse)(uint8_t *data, uint16_t len) = NULL;

/**
 * @brief 串口 初始化
 */
static void gwin_uart_init(void)
{
    uart_fd = open("/dev/ttyS0", O_RDWR | O_NONBLOCK);
    if (uart_fd < 0)
    {
        printk("uart open error!!!\r\n");
    }

    uart_rx_len = 0;
    memset(uart_rx_buf, 0x00, GWIN_UART_MAX_LEN);
}

/**
 * @brief 串口 初始化接收回调
 */
void gwin_uart_interrupt_set_callback(int8_t (*uart_recv_data_parse)(uint8_t *data, uint16_t len))
{
    _uart_recv_data_parse = uart_recv_data_parse;
}

/**
 * @brief 串口 发送
 */
void gwin_uart_send(const uint8_t *data, uint16_t len)
{
    write(uart_fd, data, len);
}

/**
 * @brief 串口 接收
 */
void gwin_uart_recv_loop(void)
{
    uint16_t remain = GWIN_UART_MAX_LEN - uart_rx_len;
    if (remain > 0)
    {
        ssize_t _uart_rx_len = read(uart_fd, &uart_rx_buf[uart_rx_len], remain);
        if (_uart_rx_len > 0)
        {
            rx_clock_last = mible_mesh_get_exact_systicks();
            uart_rx_len += _uart_rx_len;
        }
    }
    if (uart_rx_len > 0)
    {
        uint64_t now = mible_mesh_get_exact_systicks();
        if ((uint64_t)(now - rx_clock_last) >= GWIN_UART_RX_BYTE_TIMEOUT)
        {
            if (_uart_recv_data_parse != NULL)
            {
                if (_uart_recv_data_parse(uart_rx_buf, uart_rx_len) < 0)
                {
                    // LIN MARK 20240401 : MHCWB11S SDK，串口接收有bug
                    /* 串口接收数据，随机时间后，会出现偏移。
                     * 举个例子：
                     *      5分钟后，外部给模组串口rx发送15个数据，
                     *      会出现上一包数据的3个字节在前面，读出来也是15个数据，
                     *      但是是上一包数据的3个字节+这次前面的12个字节
                     *      这种情况出现后，不管时间隔多久，再发数据给串口rx，还是偏移3个字节
                     * 所以一旦出现异常，重置串口
                     */
                    ioctl(uart_fd, 0x55AA, 0);

                    // 解析失败，重发
                    static const uint8_t reack[] = { 0xAA, 0x55, 0x0F, 0x00, 0x00, 0x0E, 0xFE, };
                    gwin_uart_send(reack, sizeof(reack));
                }
            }
            uart_rx_len = 0;
            memset(uart_rx_buf, 0, GWIN_UART_MAX_LEN);
        }
    }
}


/*----------- ble -----------*/
/**
 * @brief 发送广播
 */
void gwin_ble_adv_send(const uint8_t *data, uint16_t len)
{
    mible_gap_adv_param_t adv_param =
    {
        .adv_interval_min = 0x20,
        .adv_interval_max = 0x40,
        .adv_type = MIBLE_ADV_TYPE_NON_CONNECTABLE_UNDIRECTED,
        .ch_mask.ch_37_off = 0,
        .ch_mask.ch_38_off = 0,
        .ch_mask.ch_39_off = 0,
    };
    mible_gap_adv_stop();
    mible_gap_adv_data_set(data, len, NULL, 0);
    mible_gap_adv_start(&adv_param);
}

/**
 * @brief 停止发送广播
 */
void gwin_ble_adv_stop(void)
{
    mible_gap_adv_stop();
    // 重新开启系统信息广播
    miio_system_set_adv_timeout(1800000);
}

/**
 * @brief 发送广播
 */
uint8_t gwin_ble_adv_send_with_timems(const uint8_t *data, uint8_t len, uint32_t timems)
{
    static const uint8_t ble_adv_interval_min = 20;
    mible_status_t ret = mible_gap_user_noconn_adv_start(ble_adv_interval_min, timems / ble_adv_interval_min, data, len);
    if (ret)
    {
        return ret;
    }

    return MI_SUCCESS;
}


/*-------------------------- END OF FILE ------------------------------------*/


